package fhq.hcmute.qrcode.encoder;

import fhq.hcmute.qrcode.QRCode;
import fhq.hcmute.qrcode.common.BitMatrix;
import fhq.hcmute.qrcode.common.MaskPattern;
import fhq.hcmute.qrcode.common.Version;
/**
 * @author Hien Nguyen Le
 * @Email: nglehien@gmail.com
 */
public class PenaltyScore {

	private Version version;
	private BitMatrix matrix;

	public PenaltyScore(QRCode qrCode) {
		version = qrCode.getVersion();
		matrix = qrCode.getDataMatrix();
	}

	public int calculate() {
		return calRule1() + calRule2() + calRule3() + calRule4();
	}

	/**
	 * Penalty Rule 1 - The first rule gives a penalty if five or more of the
	 * same colored pixels are next to each other in a row or column. For the
	 * first five consecutive pixels, the penalty score is increased by 3. Each
	 * consecutive pixel after that adds 1 to the penalty. See below for the
	 * illustration of this penalty rule.
	 * 
	 * @return
	 */
	private int calRule1() {
		int penaltyScore = 0;
		for (int i = 0; i < version.getModules(); i++) {
			int sameInVertical = 0;
			int sameInHorizontal = 0;
			int preBitVertical = -1;
			int preBitHorizontal = -1;
			for (int j = 0; j < version.getModules(); j++) {
				if (this.matrix.get(i, j) == preBitVertical) {
					sameInVertical++;
				} else {
					if (sameInVertical >= 5) {
						penaltyScore += MaskPattern.N1 + (sameInVertical - 5);
					}
					sameInVertical = 1;
					preBitVertical = this.matrix.get(i, j);
				}
				if (this.matrix.get(j, i) == preBitHorizontal) {
					sameInHorizontal++;
				} else {
					if (sameInHorizontal >= 5) {
						penaltyScore += MaskPattern.N1 + (sameInHorizontal - 5);
					}
					sameInHorizontal = 1;
					preBitHorizontal = this.matrix.get(j, i);
				}
			}
			if (sameInVertical >= 5) {
				penaltyScore += MaskPattern.N1 + (sameInVertical - 5);
			}
			if (sameInHorizontal >= 5) {
				penaltyScore += MaskPattern.N1 + (sameInHorizontal - 5);
			}
		}
		return penaltyScore;
	}

	/**
	 * Penalty Rule 2 The second penalty rule will add 3 to the penalty for each
	 * 2x2 block of same-colored pixels there are. See below for the
	 * illustration.
	 * 
	 * @return
	 */
	private int calRule2() {
		int penaltyScore = 0;
		for (int i = 0; i < version.getModules() - 1; i++) {
			for (int j = 0; j < version.getModules() - 1; j++) {
				int topLeft = matrix.get(i, j);
				int topRight = matrix.get(i, j + 1);
				int botLeft = matrix.get(i + 1, j);
				int botRight = matrix.get(i + 1, j + 1);

				if (topLeft == topRight && topRight == botLeft
						&& botLeft == botRight) {
					penaltyScore++;
				}

			}
		}
		return penaltyScore * MaskPattern.N2;
	}

	/**
	 * Penalty Rule 3 The third penalty rule looks for patterns of
	 * dark-light-dark-dark-dark-light-dark that have four light pixels on
	 * either or both sides.(1 : 1 : 3 : 1 : 1 ratio
	 * (dark:light:dark:light:dark) pattern in row/column, preceded or followed
	 * by light area 4 modules wide)
	 * <p>
	 * Each time this pattern is found, you add 40 to the penalty score.
	 * </p>
	 * <p>
	 * - 00001011101 - 10111010000
	 * </p>
	 * 
	 * @return
	 */
	private int calRule3() {
		int penaltyScore = 0;
		for (int i = 0; i < version.getModules(); i++) {
			for (int j = 0; j < version.getModules(); j++) {
				// Horizontal
				if (j + 6 < version.getModules()) {
					if (this.matrix.get(i, j) == BitMatrix.TRUE
							&& this.matrix.get(i, j + 1) == BitMatrix.FALSE
							&& this.matrix.get(i, j + 2) == BitMatrix.TRUE
							&& this.matrix.get(i, j + 3) == BitMatrix.TRUE
							&& this.matrix.get(i, j + 4) == BitMatrix.TRUE
							&& this.matrix.get(i, j + 5) == BitMatrix.FALSE
							&& this.matrix.get(i, j + 6) == BitMatrix.TRUE) {
						if ((j + 10 < version.getModules())
								&& this.matrix.get(i, j + 7) == BitMatrix.FALSE
								&& this.matrix.get(i, j + 8) == BitMatrix.FALSE
								&& this.matrix.get(i, j + 9) == BitMatrix.FALSE
								&& this.matrix.get(i, j + 10) == BitMatrix.FALSE) {
							penaltyScore++;
						}
						if ((j - 4 >= 0)
								&& this.matrix.get(i, j - 1) == BitMatrix.FALSE
								&& this.matrix.get(i, j - 2) == BitMatrix.FALSE
								&& this.matrix.get(i, j - 3) == BitMatrix.FALSE
								&& this.matrix.get(i, j - 4) == BitMatrix.FALSE) {
							penaltyScore++;
						}
					}
				}
				// Vertical
				if (i + 6 < version.getModules()) {
					if (this.matrix.get(i, j) == BitMatrix.TRUE
							&& this.matrix.get(i + 1, j) == BitMatrix.FALSE
							&& this.matrix.get(i + 2, j) == BitMatrix.TRUE
							&& this.matrix.get(i + 3, j) == BitMatrix.TRUE
							&& this.matrix.get(i + 4, j) == BitMatrix.TRUE
							&& this.matrix.get(i + 5, j) == BitMatrix.FALSE
							&& this.matrix.get(i + 6, j) == BitMatrix.TRUE) {
						if ((i + 10 < version.getModules())
								&& this.matrix.get(i + 7, j) == BitMatrix.FALSE
								&& this.matrix.get(i + 8, j) == BitMatrix.FALSE
								&& this.matrix.get(i + 9, j) == BitMatrix.FALSE
								&& this.matrix.get(i + 10, j) == BitMatrix.FALSE) {
							penaltyScore++;
						}
						if ((i - 4 >= 0)
								&& this.matrix.get(i - 1, j) == BitMatrix.FALSE
								&& this.matrix.get(i - 2, j) == BitMatrix.FALSE
								&& this.matrix.get(i - 3, j) == BitMatrix.FALSE
								&& this.matrix.get(i - 4, j) == BitMatrix.FALSE) {
							penaltyScore++;
						}
					}
				}
			}
		}
		return penaltyScore * MaskPattern.N3;
	}

	/**
	 * Penalty Rule 4 The final penalty rule is based on the ratio of dark to
	 * light pixels. The closer the ratio is to 50% dark and 50% light, the
	 * better the penalty score will be for this step.
	 * 
	 * There are 217 dark pixels in our QR code. The total number of pixels in
	 * the code (both light and dark) is 441.
	 * 
	 * First, you divide the number of dark cells by the total number of cells:
	 * 217 / 441 = 0.49206349206349 (approximately)
	 * 
	 * Next, you multiply the result by 100 and subtract 50. (0.49206349206349 *
	 * 100) = 49.206349206349 - 50 = -0.793650793651
	 * 
	 * Next, remove the numbers after the decimal place and take the absolute
	 * value. -0.793650793651 -> 0 (In this case, the number becomes 0.)
	 * 
	 * Finally, divide the result by 5, then multiply by 10. (0 / 5) * 10 = 0.
	 * 
	 * In this case, penalty rule 4 doesn't add anything more to the penalty
	 * score, so our final penalty score is 350.
	 * 
	 * @return
	 */
	private int calRule4() {
		int numOfDarkCells = 0;
		int numOfTotalCells = version.getModules() * version.getModules();
		for (int i = 0; i < version.getModules(); i++) {
			for (int j = 0; j < version.getModules(); j++) {
				int value = this.matrix.get(i, j);
				if (value == BitMatrix.TRUE) {
					numOfDarkCells++;
				}
			}
		}
		double k = Math
				.abs(((double) numOfDarkCells / numOfTotalCells) * 100 - 50);
		return ((int) k / 5) * MaskPattern.N4;
	}
}
